package com.yx.cloud.network.ble;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Build;

import androidx.core.app.ActivityCompat;

import com.aaa.async.DeferredObject;
import com.aaa.async.Promise;
import com.iot.common.utils.LogUtils;
import com.yx.cloud.MyApplication;
import com.yx.cloud.network.DataPacket;
import com.yx.cloud.network.ble.yg.YGBleDataHandler;
import com.yx.cloud.network.ble.yx.YxBleDataHandler;
import com.yx.cloud.network.ble.yx.YxBleDataPacket;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class BluetoothModel {
    private static final String[] requiredSPermissions = new String[]{
            Manifest.permission.BLUETOOTH_SCAN,
            Manifest.permission.BLUETOOTH_CONNECT,
            Manifest.permission.BLUETOOTH,
            Manifest.permission.BLUETOOTH_ADMIN,
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
    };
    private static final String[] requiredPermissions = new String[]{
            Manifest.permission.BLUETOOTH,
            Manifest.permission.BLUETOOTH_ADMIN,
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
    };
    protected volatile int mMtu = 20;
    private DeferredObject<Void, BleException> mConnectDeferred;
    private volatile BluetoothDevice mBluetoothDevice;
    private volatile BluetoothGattService mTransportService;
    private volatile BluetoothGattCharacteristic mWriteCharacteristic;
    private volatile BluetoothGattCharacteristic mNotifyCharacteristic;
    private volatile long mTimeoutMills = 15000;
    private volatile DelayTask delayTask;
    private volatile ReentrantLock mReentrantLock = new ReentrantLock();
    private volatile Condition mWriteCondition = mReentrantLock.newCondition();
    private BluetoothAdapter mBluetoothAdapter;
    private LocationManager mLocationManager;
    private BluetoothLeScanner scanner;
    private ScanCallbackImp mScanCallback;
    private LeScanCallbackImp mLeScanCallback;
    private Context context;
    private Executor mWriteExecutor = Executors.newSingleThreadExecutor();
    private BluetoothGatt mBluetoothGatt;
    private volatile boolean mSendResult = false;
    private volatile int mState = 0;
    private volatile Cmd receivedCmd;
    private List<BleCallback> bleCallbackList;
    private BleDataHandler dataHandler;
    private BleCallback modelBleCallback = new BleCallback() {
        @Override
        public void onBleConnected() {
            notifyConnected();
        }

        @Override
        public void onBleDisconnected() {
            notifyDisconnected();
        }

        @Override
        public void onDataReceived(Cmd cmd) {
//            BleProtocol.parseCmd(cmd, MyApplication.getRobot());
//            notifyDataReceived(cmd);
        }

        @Override
        public void onDataSend(Cmd cmd) {
            notifyDataSend(cmd);
        }
    };

    private BluetoothModel(Context context) {
        this.context = context;
        this.bleCallbackList = new ArrayList<>();
        //默认添加一个数据解析
        BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();
        mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    }

    public static BluetoothModel getInstance() {
        return InstanceHolder.INSTANCE;
    }

    public synchronized void registerBleCallback(BleCallback callback) {
        if (callback != null) {
            bleCallbackList.add(callback);
        }
    }

    public synchronized void unregisterBleCallback(BleCallback callback) {
        if (callback != null) {
            bleCallbackList.remove(callback);
        }
    }

    private synchronized void notifyDataReceived(Cmd cmd) {
        for (BleCallback bleCallback : bleCallbackList) {
            bleCallback.onDataReceived(cmd);
        }
    }

    private synchronized void notifyConnected() {
        for (BleCallback bleCallback : bleCallbackList) {
            bleCallback.onBleConnected();
        }
    }

    private synchronized void notifyDisconnected() {
        for (BleCallback bleCallback : bleCallbackList) {
            bleCallback.onBleDisconnected();
        }
    }

    private synchronized void notifyDataSend(Cmd cmd) {
        for (BleCallback bleCallback : bleCallbackList) {
            bleCallback.onDataSend(cmd);
        }
    }

    public String[] checkBluetoothPersmission(Context context) {
        List<String> unGrantedPermissions = new ArrayList<>();
        String[] permissions;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            permissions = requiredSPermissions;
        } else {
            permissions = requiredPermissions;
        }
        for (String permission : permissions) {
            if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                unGrantedPermissions.add(permission);
            }
        }

        if (unGrantedPermissions.size() != 0) {
            return unGrantedPermissions.toArray(new String[unGrantedPermissions.size()]);
        } else {
            return null;
        }

    }

//    public void openSystemBluetooth(Activity activity) {
//        //请求打开蓝牙
//        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//        activity.startActivityForResult(intent, DeviceActivity.REQUEST_ENABLE_BT);
//    }

//    public void openSystemLocation(Activity activity) {
//        //请求打开定位
//        Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
//        activity.startActivity(intent);
//    }
//
//    public boolean isBluetoothOpen() {
//        if (mBluetoothAdapter == null) {
//            Toast.makeText(context, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
//            return false;
//        }
//        return mBluetoothAdapter.isEnabled();
//    }
//
//    public boolean isLocationOpen() {
//        return mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
//                mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) ||
//                mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER) ||
//                mLocationManager.isProviderEnabled(LocationManager.FUSED_PROVIDER);
//    }


    private boolean checkAndroid_S_BLE_permision() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
                && (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
                || ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)) {
            return false;
        } else {
            return true;
        }

    }

    public void startScanBle(BLEScanCallback callback) {
        if (!checkAndroid_S_BLE_permision()) {
            return;
        }
        if (mBluetoothAdapter.isEnabled()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                scanner = mBluetoothAdapter.getBluetoothLeScanner();
                mScanCallback = new ScanCallbackImp(callback);
                scanner.startScan(mScanCallback);
            } else {
                mLeScanCallback = new LeScanCallbackImp(callback);
                mBluetoothAdapter.startLeScan(mLeScanCallback);
            }
        }
    }

    public void stopScanBle() {
        if (!checkAndroid_S_BLE_permision()) {
            return;
        }
        if (mBluetoothAdapter.isEnabled()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                scanner.stopScan(mScanCallback);
            } else {
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            }
        }
    }

    public synchronized Promise<Void, BleException> connect(String address) {
        BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(address);

        mBluetoothDevice = remoteDevice;
        mConnectDeferred = new DeferredObject();

        if (!checkAndroid_S_BLE_permision()) {
            mConnectDeferred.reject(new BleException("BLE permission miss"));
            return mConnectDeferred.promise();
        }

        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            mConnectDeferred.reject(new BleException("BLE disabled "));
            return mConnectDeferred.promise();
        }

        if (mState == Constants.STATE_CONNECTED) {
            mConnectDeferred.reject(new BleException("BLE channel is connected"));
            return mConnectDeferred.promise();
        }

        if (mState == Constants.STATE_CONNECTING) {
            mConnectDeferred.reject(new BleException("BLE channel is connected"));
            return mConnectDeferred.promise();
        }

        if (mBluetoothDevice == null) {
            mConnectDeferred.reject(new BleException("Can not connect to Server. Target device is null"));
            return mConnectDeferred.promise();
        }

        setState(Constants.STATE_CONNECTING);

        delayTask = new DelayTask(new Runnable() {
            @Override
            public void run() {
                //连接一定时间没有响应  退出
                onBleClientDisconnect(new BleException("BLE client connect timeout"));
            }
        }, mTimeoutMills);

        mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, new BleGattCallback());

        return mConnectDeferred.promise();
    }

    public synchronized Promise<Void, BleException> disconnect() {
        DeferredObject<Void, BleException> disconnectDeferred = new DeferredObject();
        if (mState == Constants.STATE_DISCONNECTED) {
            disconnectDeferred.reject(new BleException("BLE channel is not connected"));
            return disconnectDeferred.promise();
        }

        if (mState == Constants.STATE_CONNECTING) {
            //reject former connect deffer
            onBleClientDisconnect(new BleException("BLE client connect canceled"));

            disconnectDeferred.resolve(null);
            return disconnectDeferred.promise();
        }

        setState(Constants.STATE_DISCONNECTED);
        mBluetoothGatt.disconnect();
        disconnectDeferred.resolve(null);

        return disconnectDeferred.promise();
    }

    public synchronized Promise<Void, BleException> send(Cmd cmd) {
        DeferredObject<Void, BleException> deferredObject = new DeferredObject<>();

        LogUtils.i("Channel write state: " + mState);

        if (mState != Constants.STATE_CONNECTED) {
            deferredObject.reject(new BleException("BLE not connect"));
            return deferredObject.promise();
        }

        if (cmd == null || cmd.getContent() == null) {
            deferredObject.reject(new BleException("BLE empty Data"));
            return deferredObject.promise();
        }

        mWriteExecutor.execute(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("bleWrite");
                LogUtils.i("writeResult111  start ");
                List<YxBleDataPacket> dataPacketList = dataHandler.packageData(cmd);

                boolean writeResult = writeDataSerially(dataPacketList);
                LogUtils.i("writeResult111  finish "+ writeResult);
                if (writeResult) {
                    deferredObject.resolve(null);
                    modelBleCallback.onDataSend(cmd);
                } else {
                    deferredObject.reject(new BleException("Write data fail"));
                }
            }
        });
        return deferredObject.promise();
    }

    protected boolean write(byte[] data) {

        mWriteCharacteristic.setValue(data);
        mWriteCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
        boolean result = mBluetoothGatt.writeCharacteristic(mWriteCharacteristic);
        LogUtils.i("write data size :"+ data.length + " result :" + result);
        return result;
    }

    protected boolean writeDataSerially(List<YxBleDataPacket> dataPacketList) {
        mSendResult = false;

        for (int i = 0; i < dataPacketList.size(); ) {
            if (mState != Constants.STATE_CONNECTED) {
                return false;
            }

            DataPacket dataPacket = dataPacketList.get(i);
            boolean writeResult = write(dataPacket.packData);
            if (!writeResult) {
                continue;
            }

            LogUtils.i("writeDataSerially  wait start");
            mReentrantLock.lock();
            try {
                mWriteCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
            mReentrantLock.unlock();
            LogUtils.i("writeDataSerially  wait end");

            if (!mSendResult) {
                continue;
            }
            i++;
        }
        return true;
    }

    protected void onWriteResult(boolean result) {
        LogUtils.i("onWriteResult : " + result);
        mSendResult = result;
        mReentrantLock.lock();
        mWriteCondition.signal();
        mReentrantLock.unlock();
    }

    private void onBleClientDisconnect(BleException e) {
        //Cancel timeout task
        if (delayTask != null) {
            delayTask.cancel();
            delayTask = null;
        }

        if (mConnectDeferred != null) {
            mConnectDeferred.reject(e);
            mConnectDeferred = null;
        }
        //send inactive when channel translate form connected to disconnect
        if (mState == Constants.STATE_CONNECTED) {
            LogUtils.i("onBleClientDisconnect fireChannelInactive");
            modelBleCallback.onBleDisconnected();
        }

        setState(Constants.STATE_DISCONNECTED);

        onWriteResult(false);
        mBluetoothGatt.close();

    }

    private void onBleClientConnected() {
        //Cancel timeout task
        if (delayTask != null) {
            delayTask.cancel();
            delayTask = null;
        }

        if (mConnectDeferred != null) {
            mConnectDeferred.resolve(null);
            mConnectDeferred = null;
        }


        setState(Constants.STATE_CONNECTED);
        LogUtils.i("onBleClientConnected fireChannelActive");
        modelBleCallback.onBleConnected();
    }

    private void setState(int state) {
        LogUtils.i("set state: " + state);
        this.mState = state;
    }

    public interface BLEScanCallback {
        void onDeviceFind(BluetoothDevice device);
    }

    public interface BleCallback {
        void onBleConnected();

        void onBleDisconnected();

        void onDataReceived(Cmd cmd);

        void onDataSend(Cmd cmd);
    }

    private static class InstanceHolder {
        static BluetoothModel INSTANCE = new BluetoothModel(MyApplication.get());
    }

    class ScanCallbackImp extends ScanCallback {
        private BLEScanCallback callback;

        public ScanCallbackImp(BLEScanCallback callback) {
            this.callback = callback;
        }

        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
           // LogUtils.i("ScanCallback device : " + result.getDevice().toString());
            if (callback != null) {
                callback.onDeviceFind(result.getDevice());
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
            LogUtils.i("onScanFailed  " + errorCode);
        }
    }

    private class LeScanCallbackImp implements LeScanCallback {
        private BLEScanCallback callback;

        public LeScanCallbackImp(BLEScanCallback callback) {
            this.callback = callback;
        }

        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            LogUtils.i("  " + device.toString());
            if (callback != null) {
                callback.onDeviceFind(device);
            }
        }
    }

    private class BleGattCallback extends BluetoothGattCallback {
        @Override
        public synchronized void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            LogUtils.i("onConnectionStateChange : operation result: " + status + " connect status: " + newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                mBluetoothGatt.discoverServices();
            } else {
                onBleClientDisconnect(new BleException("BLE client connect fail, code : " + status));
            }
        }

        @Override
        public synchronized void onServicesDiscovered(BluetoothGatt gatt, final int status) {
            super.onServicesDiscovered(gatt, status);
            LogUtils.i("onServicesDiscovered state : " + status);
            boolean isServiceValid = false;
            if (status == BluetoothGatt.GATT_SUCCESS) {
                YGBleDataHandler yghandler=new YGBleDataHandler();
                YxBleDataHandler yxHandler=new YxBleDataHandler();
                if(yghandler.validateService(gatt)){
                    isServiceValid=true;
                    dataHandler=yghandler;
                    mTransportService=dataHandler.mTransportService;
                    mWriteCharacteristic=dataHandler.mWriteCharacteristic;
                    mNotifyCharacteristic=dataHandler.mNotifyCharacteristic;

                }else if(yxHandler.validateService(gatt)){
                    isServiceValid=true;
                    dataHandler=yxHandler;
                    mTransportService=dataHandler.mTransportService;
                    mWriteCharacteristic=dataHandler.mWriteCharacteristic;
                    mNotifyCharacteristic=dataHandler.mNotifyCharacteristic;
                }
            }

            //if validate service fail,then disconnect
            if (!isServiceValid) {
                LogUtils.i("Can not found transport service : close channel ");
                onBleClientDisconnect(new BleException("Can not found transport service"));
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            //发现服务端发送的
            LogUtils.i("onCharacteristicChanged " + new String(characteristic.getValue()));
            byte[] receivedData = characteristic.getValue();
            if (receivedCmd == null) {
                receivedCmd = new Cmd();
            }
            boolean isComplete = dataHandler.unPackageData(receivedCmd, receivedData);
            if (isComplete) {
                modelBleCallback.onDataReceived(receivedCmd);
                receivedCmd = null;
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            LogUtils.i("onCharacteristicWrite  result : " + status);
            onWriteResult(status == BluetoothGatt.GATT_SUCCESS);
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            LogUtils.i("onDescriptorWrite : " + " result : " + status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                mBluetoothGatt.requestMtu(180);
                onBleClientConnected();
            } else {
                onBleClientDisconnect(new BleException("Connect fail : cannot set Descriptor"));
            }
        }

        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            LogUtils.i("onMtuChanged: mtc : " + mtu + " result : " + status);
            //if request mtu success
            if (status == BluetoothGatt.GATT_SUCCESS) {
                mMtu = mtu - 3;
                YxBleDataPacket.REAL_SIZE = mtu - 6;
                onBleClientConnected();
            } else {
                onBleClientDisconnect(new BleException("Request mtu fail"));
            }
        }
    }
}
